package org.jvm.nativemethod.methods.java.lang;

import org.jvm.JVM;
import org.jvm.instruction.base.InstructionUtil;
import org.jvm.nativemethod.*;
import org.jvm.rtda.Object;
import org.jvm.rtda.heap.Klass;
import org.jvm.rtda.heap.classmember.Method;
import org.jvm.rtda.heap.stringpool.StringPool;
import org.jvm.rtda.thread.Thread;
import org.jvm.rtda.thread.*;

import java.util.*;

/**
 * @author 海燕
 * @date 2023/2/26
 */
public class SystemNativeMethod extends NativeMethodRegister {

    private static String jlSystem = "java/lang/System";

    public static void init() throws NoSuchMethodException {
        register(jlSystem, "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", SystemNativeMethod.class.getDeclaredMethod("arraycopy", Frame.class));
        register(jlSystem, "initProperties", "(Ljava/util/Properties;)Ljava/util/Properties;", SystemNativeMethod.class.getDeclaredMethod("initProperties", Frame.class));
        register(jlSystem, "setIn0", "(Ljava/io/InputStream;)V", SystemNativeMethod.class.getDeclaredMethod("setIn0", Frame.class));
        register(jlSystem, "setOut0", "(Ljava/io/PrintStream;)V", SystemNativeMethod.class.getDeclaredMethod("setOut0", Frame.class));
        register(jlSystem, "setErr0", "(Ljava/io/PrintStream;)V", SystemNativeMethod.class.getDeclaredMethod("setErr0", Frame.class));
        register(jlSystem, "currentTimeMillis", "()J", SystemNativeMethod.class.getDeclaredMethod("currentTimeMillis", Frame.class));
        register(jlSystem, "nanoTime", "()J", SystemNativeMethod.class.getDeclaredMethod("nanoTime", Frame.class));
    }

    public static void arraycopy(Frame frame) {
        LocalVars localVars = frame.getLocalVars();
        Object src = localVars.getRef(0);
        int srcPos = localVars.getInt(1);
        Object dest = localVars.getRef(2);
        int destPos = localVars.getInt(3);
        int length = localVars.getInt(4);
        if (src == null || dest == null) {
            frame.getThread().throwNullPointerException();
            return;
        }
        if (!checkArrayCopy(src, dest)) {
            throw new RuntimeException("java.lang.ArrayStoreException");
        }
        if (srcPos < 0 || destPos < 0 || length < 0
                || srcPos + length > src.arrayLength()
                || destPos + length > dest.arrayLength()) {
            frame.getThread().throwArrayIndexOutOfBoundsException();
        }
        System.arraycopy(src.getData(), srcPos, dest.getData(), destPos, length);
    }

    private static boolean checkArrayCopy(Object src, Object dest) {
        Klass srcKlass = src.getKlass();
        Klass destKlass = dest.getKlass();
        if (!srcKlass.isArray() || !destKlass.isArray()) {
            return false;
        }
        if (srcKlass.getComponentClass().isPrimitive()
                || destKlass.getComponentClass().isPrimitive()) {
            return srcKlass == destKlass;
        }
        return true;
    }

    public static void nanoTime(Frame frame) {
        frame.getOperandStack().pushLong(System.nanoTime());
    }

    public static void currentTimeMillis(Frame frame) {
        frame.getOperandStack().pushLong(System.currentTimeMillis());
    }

    public static void initProperties(Frame frame) {
        LocalVars vars = frame.getLocalVars();
        Object props = vars.getRef(0);

        OperandStack stack = frame.getOperandStack();
        stack.pushRef(props);

        Method setPropMethod = props.getKlass()
                .getInstanceMethod("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;");
        Thread thread = frame.getThread();

        Map<String, String> sysProps = new HashMap<>();
        sysProps.put("java.version", "1.8.0");
        sysProps.put("java.vendor", "jvm.go");
        sysProps.put("java.vendor.url", "https://gitee.com/haiyanzj/my-java-jvm.git");
        sysProps.put("java.home", JVM.cmd.getXjreOption());
        sysProps.put("java.class.version", "52.0");
        //用户类目录
        sysProps.put("java.class.path", JVM.cmd.getCpOption());
        //扩展类目录
        sysProps.put("java.ext.dirs", JVM.cmd.getXjreOption() + "/lib/ext");
        sysProps.put("java.awt.graphicsenv", "sun.awt.CGraphicsEnvironment");
        sysProps.put("os.name", System.getProperty("os.name"));
        sysProps.put("os.arch", System.getProperty("os.arch"));
        sysProps.put("os.version", System.getProperty("os.version"));
        sysProps.put("file.separator", System.getProperty("file.separator"));
        sysProps.put("path.separator", System.getProperty("path.separator"));
        sysProps.put("line.separator", System.getProperty("line.separator"));
        sysProps.put("user.name", "haiyan");
        sysProps.put("user.home", "");
        sysProps.put("user.dir", System.getProperty("user.dir"));
        sysProps.put("user.country", "CN");
        sysProps.put("file.encoding", "UTF-8");
        sysProps.put("sun.stdout.encoding", "UTF-8");
        sysProps.put("sun.stderr.encoding", "UTF-8");

        for (Map.Entry<String, String> entry : sysProps.entrySet()) {
            String key = entry.getKey();
            String val = entry.getValue();
            Object jKey = StringPool.jString(key);
            Object jVal = StringPool.jString(val);


            OperandStack tempOperandStack = new OperandStack(3);
            tempOperandStack.pushRef(props);
            tempOperandStack.pushRef(jKey);
            tempOperandStack.pushRef(jVal);

            //这里要压一个无操作无返回的间隙栈帧，目的是承接并抛弃栈顶return指令的返回。避免返回值压入下面的栈帧
            //为什么要这么做呢？因为下面进行批量压栈操作并不是通过真实的方法调用，下方栈帧不能承接上方栈帧的返回值
            Frame tempFrame = NativeMethodUtil.newShimFrame(thread, tempOperandStack);
            //借用java构造器进行初始化
            InstructionUtil.invokeMethod(tempFrame, setPropMethod);
        }
    }

    public static void setIn0(Frame frame) {
        LocalVars vars = frame.getLocalVars();
        Object in = vars.getRef(0);

        Klass sysClass = frame.getMethod().getKlass();
        sysClass.setStaticRefVar("in", "Ljava/io/InputStream;", in);
    }

    public static void setOut0(Frame frame) {
        LocalVars vars = frame.getLocalVars();
        Object out = vars.getRef(0);

        Klass sysClass = frame.getMethod().getKlass();
        sysClass.setStaticRefVar("out", "Ljava/io/PrintStream;", out);
    }


    public static void setErr0(Frame frame) {
        LocalVars vars = frame.getLocalVars();
        Object err = vars.getRef(0);

        Klass sysClass = frame.getMethod().getKlass();
        sysClass.setStaticRefVar("err", "Ljava/io/PrintStream;", err);
    }

}
